package org.googlecode.openapi_java;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.*;

import javax.naming.spi.DirStateFactory.Result;

import static org.googlecode.openapi_java.Constants.*;

public class OpenApi {
	private String app_id;

	private boolean inited = false;
	private long expire;
	private long mid;
	private String secret;
	private String sid;
	private HttpClient httpClient;

	public OpenApi(String app_id) {
		this.app_id = app_id;
	}

	private OpenApi() {
	}

	public boolean isInited() {
		return inited;
	}

	public String getAuthBrowserUrl(boolean isTouch, int accessLevelSettings) {
		StringBuilder sb = new StringBuilder(authUrl);
		sb.append("?app=").append(app_id);
		sb.append("&layout=").append(isTouch ? "touch" : "popup");
		sb.append("&type=").append("browser");
		if (accessLevelSettings != 0) {
			sb.append("&settings=").append(accessLevelSettings);
		}
		return sb.toString();
	}

	public String getAuthBrowserUrl(boolean isTouch) {
		return getAuthBrowserUrl(isTouch, 0);
	}

	public long getExpire() {
		return expire;
	}

	public void init(String response) throws VkontakteException {
		// if (inited) throw new IllegalStateException("already initialized!")
		String jsonString = response;
		try {
			JSONObject object = new JSONObject(jsonString);
			expire = object.getLong("expire");
			mid = object.getLong("mid");
			secret = object.getString("secret");
			sid = object.getString("sid");
			inited = true;
			httpClient = new DefaultHttpClient();
		} catch (JSONException e) {
			throw new VkontakteException(e);
		}

	}

	public JSONObject makeRequest(Map<String, String> params)
			throws IOException, VkontakteException {

		TreeMap<String, String> sortedMap = new TreeMap<String, String>(params);
		sortedMap.put("api_id", app_id);
		sortedMap.put("format", FORMAT);
		sortedMap.put("v", API_VERSION);
		// sortedMap.put("test_mode", "1");
		String sig = makeSig(sortedMap);
		sortedMap.put("sig", sig);
		sortedMap.put("sid", sid);

		HttpPost request = new HttpPost(apiUrl);
		List<NameValuePair> formParams = new ArrayList<NameValuePair>();
		for (Map.Entry<String, String> me : sortedMap.entrySet()) {
			formParams.add(new BasicNameValuePair(me.getKey(), me.getValue()));
		}
		request.setEntity(new UrlEncodedFormEntity(formParams, "UTF-8"));
		HttpResponse response = httpClient.execute(request);
		HttpEntity e = response.getEntity();
		String content = EntityUtils.toString(e);

		try {
			JSONObject object = new JSONObject(content);
		//	System.out.println("object = " + object);
			return object;
		} catch (JSONException e1) {
			throw new VkontakteException(e1);
		}
	}
	
	public synchronized String makeSig(TreeMap<String, String> sortedMap) {
		StringBuffer signatureString = new StringBuffer();
		signatureString.append(String.valueOf(mid));
		for (Map.Entry<String, String> me : sortedMap.entrySet()) {
			signatureString.append(me.getKey()).append("=").append(
					me.getValue());
		}
		signatureString.append(secret);
		String signatureHash = MD5Helper
				.makeMD5Hash(signatureString.toString());
		return signatureHash;
	}

	/**
	 * http://vkontakte.ru/pages.php?id=2370993
	 * 
	 * @param uids
	 *            - перечисленные через запятую ID
	 *            пользователей (максимум 1000 штук).
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Profile> getProfiles(List<Long> uids) throws IOException,
			VkontakteException {
		return getProfiles(uids, new LinkedList<String>());
	}

	public List<Profile> getProfiles(List<Long> uids, List<String> fields)
			throws IOException, VkontakteException {
		if (uids.size() > 1000)
			throw new IllegalArgumentException(
					"uid list must be not more than 1000");
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "getProfiles");
		params.put("uids", StringUtils.join(uids, ","));
		params.put("fields", StringUtils.join(fields, ","));
		
		JSONObject o = makeRequest(params);
		try {
			JSONArray profilesJSON = o.getJSONArray("response");
			int count = profilesJSON.length();
			List<Profile> profiles = new ArrayList<Profile>(count);
			for (int i = 0; i < count; i++) {
				profiles.add(Profile.fromJSON(profilesJSON.getJSONObject(i)));
			}
			if (count < uids.size()) {
				System.out.println("warning: not all uids are available");
			}
			return profiles;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	public List<Profile> getProfilesWithAllData(List<Long> uids) throws IOException,
			VkontakteException {
		List<String> fields = new LinkedList<String>();
		fields.add("nickname");
		fields.add("sex");
		fields.add("bdate");
		fields.add("city");
		fields.add("photo");
		return getProfiles(uids, fields);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=2370997
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Profile> getFriends(long uid) throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "getFriends");
		params.put("uid", String.valueOf(uid));
		List<String> fields = new LinkedList<String>();
		fields.add("uid");
		fields.add("nickname");
		fields.add("first_name");
		fields.add("second_name");
		fields.add("photo");
		params.put("fields", StringUtils.join(fields, ","));
		
		JSONObject o = makeRequest(params);
		System.out.println("Friends " + o);
		try {
			JSONArray friendsJSON = o.getJSONArray("response");
			int count = friendsJSON.length();
			List<Profile> profiles = new ArrayList<Profile>(count);
			for (int i = 0; i < count; i++) {
				profiles.add(Profile.fromJSON(friendsJSON.getJSONObject(i)));
			}
			return profiles;
		} catch (JSONException e1) {
			throw new VkontakteException(o);
		}
	}

	
	
	
	/**
	 * http://vkontakte.ru/pages.php?id=3900144
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Long> getGroupsIds() throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "getGroups");
		JSONObject o = makeRequest(params);
		try {
			JSONArray groupsJSON = o.getJSONArray("response");
			int count = groupsJSON.length();
			List<Long> profiles = new ArrayList<Long>(count);
			for (int i = 0; i < count; i++) {
				profiles.add(groupsJSON.getLong(i));
			}
			return profiles;
		} catch (JSONException e) {
			throw new VkontakteException(e);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=7767109
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Group> getGroups() throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "getGroupsFull");
		JSONObject o = makeRequest(params);
		try {
			JSONArray groupsJSON = o.getJSONArray("response");
			int count = groupsJSON.length();
			List<Group> groups = new ArrayList<Group>(count);
			for (int i = 0; i < count; i++) {
				groups.add(Group.fromJSON(groupsJSON.getJSONObject(i)));
			}
			return groups;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215410
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public Status getStatus() throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "activity.get");
		JSONObject o = makeRequest(params);
		try {
			JSONObject statusJSON = o.getJSONObject("response");
			Status status = Status.fromJSON(statusJSON);
			return status;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215455
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Status> getStatusHistory(long user_id) throws IOException,
			VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "activity.getHistory");
		if (user_id != 0)
			params.put("uid", String.valueOf(user_id));
		JSONObject o = makeRequest(params);
		try {
			JSONArray statusesJSON = o.getJSONArray("response");
			int count = statusesJSON.length();
			List<Status> statuses = new ArrayList<Status>(count);
			for (int i = 0; i < count; i++) {
				statuses.add(Status.fromJSON(statusesJSON.getJSONObject(i)));
			}
			return statuses;
		} catch (JSONException e) {
			throw new VkontakteException(e);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215455
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Status> getStatusHistory() throws IOException,
			VkontakteException {
		return getStatusHistory(0);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215496
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	private List<Status> getStatusNews(long timestamp, int offset, int count)
			throws IOException, VkontakteException {
		if (timestamp != 0) {
			if (offset != 0)
				throw new IllegalArgumentException(
						"offset is ignored if timestamp is set");
			if (count != 0)
				throw new IllegalArgumentException(
						"count is ignored if timestamp is set");
		}
		if (count > 100)
			throw new IllegalArgumentException(
					"count must be not more than 100");
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "activity.getNews");
		if (timestamp != 0)
			params.put("timestamp", String.valueOf(timestamp));
		if (offset != 0)
			params.put("offset", String.valueOf(offset));
		if (count != 0)
			params.put("count", String.valueOf(count));
		JSONObject o = makeRequest(params);
		try {
			JSONArray statusesJSON = o.getJSONArray("response");
			int statusCountInResponse = statusesJSON.length() - 1;
			System.out.println("statusCountInResponse = "
					+ statusCountInResponse);
			int statusCountTotal = statusesJSON.getInt(0);
			System.out.println("statusCountTotal = " + statusCountTotal);
			List<Status> statuses = new ArrayList<Status>(statusCountInResponse);
			for (int i = 0; i < statusCountInResponse; i++) {
				int position = i + 1;// first element is statusCountTotal so we
				// start from 1
				statuses.add(Status.fromJSON(statusesJSON
						.getJSONObject(position)));
			}
			return statuses;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215496
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Status> getStatusNews() throws IOException, VkontakteException {
		return getStatusNews(0, 0, 0);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215496
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Status> getStatusNews(long timestamp) throws IOException,
			VkontakteException {
		return getStatusNews(timestamp, 0, 0);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215496
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Status> getStatusNews(int offset, int count)
			throws IOException, VkontakteException {
		return getStatusNews(0, offset, count);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215427
	 * 
	 * @param status
	 * @return status_id in case of success
	 * @throws IOException
	 * @throws JSONException
	 */
	public long setStatus(String status) throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "activity.set");
		params.put("text", status);
		JSONObject o = makeRequest(params);
		try {
			long status_id = o.getLong("response");
			return status_id;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=11215482
	 * 
	 * @return seems to return true anyway
	 * @throws IOException
	 * @throws JSONException
	 */
	public boolean deleteStatus(long status_id) throws IOException,
			VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "activity.deleteHistoryItem");
		params.put("aid", String.valueOf(status_id));
		JSONObject o = makeRequest(params);
		try {
			int result = o.getInt("response");
			return result == 1;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367118
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Album> getAlbums(long userId, List<Long> albumIds)
			throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "photos.getAlbums");
		if (albumIds != null && albumIds.size() != 0) {
			params.put("aids", StringUtils.join(albumIds, ","));
		}
		if (userId != 0) {
			params.put("uid", String.valueOf(userId));
		}

		JSONObject o = makeRequest(params);
		try {
			JSONArray albumsJSON = o.getJSONArray("response");
			int count = albumsJSON.length();
			List<Album> albums = new ArrayList<Album>(count);
			for (int i = 0; i < count; i++) {
				albums.add(Album.fromJSON(albumsJSON.getJSONObject(i)));
			}
			return albums;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}

	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367118
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Album> getAlbums(long userId) throws IOException,
			VkontakteException {
		return getAlbums(userId, null);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367118
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Album> getAlbums(List<Long> albumIds) throws IOException,
			VkontakteException {
		return getAlbums(0, albumIds);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367118
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Album> getAlbums() throws IOException, VkontakteException {
		return getAlbums(0, null);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367119
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Photo> getPhotos(long userId, long albumId, List<Long> photoIds)
			throws IOException, VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "photos.get");
		params.put("uid", String.valueOf(userId));
		params.put("aid", String.valueOf(albumId));
		if (photoIds != null && photoIds.size() != 0) {
			params.put("aids", StringUtils.join(photoIds, ","));
		}
		JSONObject o = makeRequest(params);
		try {
			JSONArray photosJSON = o.getJSONArray("response");
			int count = photosJSON.length();
			List<Photo> photos = new ArrayList<Photo>(count);
			for (int i = 0; i < count; i++) {
				photos.add(Photo.fromJSON(photosJSON.getJSONObject(i)));
			}
			return photos;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	/**
	 * http://vkontakte.ru/pages.php?id=6367119
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Photo> getPhotos(long userId, long albumId) throws IOException,
			VkontakteException {
		return getPhotos(userId, albumId, null);
	}

	/**
	 * http://vkontakte.ru/pages.php?id=7915975
	 * 
	 * @return
	 * @throws IOException
	 * @throws JSONException
	 */
	public List<Photo> getPhotosById(List<String> photoIds) throws IOException,
			VkontakteException {
		
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "photos.getById");
		if (photoIds != null && photoIds.size() != 0) {
			params.put("photos", StringUtils.join(photoIds, ","));
		}
		JSONObject o = makeRequest(params);
		System.out.println(o);
		try {
			JSONArray photosJSON = o.getJSONArray("response");
			int count = photosJSON.length();
			List<Photo> photos = new ArrayList<Photo>(count);
			for (int i = 0; i < count; i++) {
				photos.add(Photo.fromJSON(photosJSON.getJSONObject(i)));
			}
			return photos;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}
	}

	public List<Message> getMessage(boolean out, int count, int offset,
			List<Integer> filters, int previewLength) throws IOException,
			VkontakteException {

		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "messages.get");
		if (filters != null && filters.size() != 0) {
			params.put("filters", StringUtils.join(filters, ","));
		}
		params.put("out", out ? "1" : "0");
		params.put("offset", String.valueOf(offset));
		params.put("count", String.valueOf(count));
		params.put("preview_length", String.valueOf(previewLength));
		JSONObject o = makeRequest(params);
		try {
			JSONArray messagesJSON = o.getJSONArray("response");
			int length = messagesJSON.length();
			List<Message> messages = new ArrayList<Message>(length);
			for (int i = 1; i < count; i++) {
				messages.add(Message.fromJSON(messagesJSON.getJSONObject(i)));
			}
			return messages;
		} catch (JSONException e) {
			throw new VkontakteException(o);
		}

	}

	public List<Message> getFullIncomeMessages() throws IOException,
			VkontakteException {
		return getMessage(false, 100, 0, null, 0);
	}

	public List<Message> getFullOutcomeMessages() throws IOException,
			VkontakteException {
		return getMessage(true, 100, 0, null, 0);
	}

	public JSONObject execute(String code) throws IOException,
			VkontakteException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("method", "execute");
		params.put("code", code);
		return makeRequest(params);
	}

	public List<ProfiledContainer<Status>> getNewsWithProfile(long timestamp)
			throws JSONException, IOException, VkontakteException {
		String code = "var a = API.activity.getNews({\"timestamp\": "
				+ String.valueOf(timestamp)
				+ " });\n"
				+ "var b = a@.uid; var c = API.getProfiles({\"uids\": b,  \"fields\": \"photo\" }); return [a, c];";
		JSONObject j = execute(code);
		System.out.println("J = " + j);
		JSONArray statusesJSON = j.getJSONArray("response");
		ContainerFactory<Status> factory = new ContainerFactory<Status>(
				new Factory<Status>() {

					public Status create(JSONObject jsonObject)
							throws JSONException {
						return Status.fromJSON(jsonObject);
					}
				});
		return factory.create(statusesJSON);
	}

	//
	// TODO filter's param is ignored
	public List<ProfiledContainer<Message>> getMessagesWithProfile(
			final boolean out, int count, int offset, List<Integer> filters,
			int previewLength) throws JSONException, IOException,
			VkontakteException {
		String outParam = out ? "1" : "0";
		String code = "var a = API.messages.get({\"out\": "
				+ outParam
				+ ","
				+ " \"offset\": "
				+ offset
				+ ","
				+ " \"count\": "
				+ count
				+ ","
				+ " \"preview_length\": "
				+ previewLength
				+ "  });\n"
				+ "var b = a@.uid; var c = API.getProfiles({\"uids\": b,  \"fields\": \"photo\"}); return [a, c];";
		JSONArray messagesJSON = execute(code).getJSONArray("response");
		ContainerFactory<Message> factory = new ContainerFactory<Message>(
				new Factory<Message>() {

					public Message create(JSONObject jsonObject)
							throws JSONException {
						return Message.fromJSON(jsonObject, out);
					}
				});
		return factory.create(messagesJSON);

	}

	public List<ProfiledContainer<Message>> getFullIncomeMessagesWithProfile()
			throws IOException, VkontakteException, JSONException {
		return getMessagesWithProfile(false, 100, 0, null, 0);
	}
	
	
	public List<AlbumWithThumb> getAlbumsWithThumb(long uid) throws IOException, VkontakteException{
		List<Album> l = getAlbums(uid);
		List<String> photosId = new LinkedList<String>();
		String uidStr = String.valueOf(uid) + "_";
		for (Album alb: l){
			photosId.add(uidStr + String.valueOf(alb.getThumbId()));
		}
	
		List<Photo> thumbs = getPhotosById(photosId);
		List<AlbumWithThumb> result = new LinkedList<AlbumWithThumb>(); 
		for (Album album: l){
			Photo thumb = null;
			long aid = album.getAid();
			for (Photo ph: thumbs){
				if (aid == ph.getAid()){
					thumb = ph;
				}
			}
			result.add(new AlbumWithThumb(album, thumb));
		}
		return result;
	}

}
